<?php

namespace System\Types;

class Decimal extends Object
{

    /**
     * @var string
     */
    private $value;

    /**
     * @param decimal $value
     */
    public function __construct($value)
    {
        parent::__construct();
        if (!\is_float($value) && !\is_int($value) && !\is_string($value))
            throw new Exception('O parâmetro informato é inválido.');

        $dec = \preg_match('/[^-.0-9]/g', $value);
        $points = 0;

        $decs = \explode('.', $dec);

        if (\count($decs) > 2)
            throw new \System\Core\Errors\Exceptions('O parâmetro informato é inválido.');
        else if (\count($decs) == 2)
        {
            $points = \strlen($decs[1]);
        }
        else
        {
            $points = 2;
            $dec .= '00';
        }

        $dec = \preg_replace('/[^-0-9]/g', '', $dec);

        $value = \substr($dec, 0, $points) . \substr($dec, $points);

        $this->value = $value;
    }

    public function ToString()
    {
        return $this->value;
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    private function match(Decimal $dec)
    {
        $m_0_n = $dec_0[0] === '-';

        $dec_0 = \str_replace('-', '', $this->value);

        $p_0 = \strlen($dec_0);

        $dec_0 = $m_0_n . $dec_0;

        $dec_1 = \str_replace('-', '', $dec->ToString());

        $m_1_n = $dec_0[1] === '-';

        $p_1 = \strlen($dec_1);

        $dec_1 = $m_1_n . $dec_1;

        if ($p_0 > $p_1)
        {
            $p_0 += $m_1_n ? 1 : 0;
            $dec_1 = \str_pad($dec_1, $p_0, '0', \STR_PAD_RIGHT);
        }
        else
        {
            $p_1 += $m_0_n ? 1 : 0;
            $dec_0 = \str_pad($dec_0, $p_1, '0', \STR_PAD_RIGHT);
        }

        $_0 = \explode('.', $dec_0);

        $minus = \strlen($_0[1]);

        $minus -= ($m_0_n) ? 1 : 0;

        return array(new Decimal($dec_0), new Decimal($dec_1), $minus);
    }

    private function calc(Decimal $dec, $operator = '+')
    {
        $decs = $this->match($dec);

        $dec_0 = (int) \preg_replace('/[^-0-9]/g', '', $decs[0]->ToString());

        $dec_1 = (int) \preg_replace('/[^-0-9]/g', '', $decs[1]->ToString());

        $dec_2 = 0;

        switch ($operator)
        {
            case '+':
                $dec_2 = $dec_0 + $dec_1;
                break;
            case '*':
                $dec_2 = $dec_0 * $dec_1;
                break;
            case '-':
                $dec_2 = $dec_0 - $dec_1;
                break;
            case '/':
                $dec_2 = $dec_0 / $dec_1;
                break;
            case '%':
                $dec_2 = $dec_0 % $dec_1;
                break;
            case '^':
                $dec_2 = $dec_0 ^ $dec_1;
                break;
            case '<<':
                $dec_2 = $dec_0 << $dec_1;
                break;
            case '>>':
                $dec_2 = $dec_0 >> $dec_1;
                break;
            case '&':
                $dec_2 = $dec_0 & $dec_1;
                break;
            case '|':
                $dec_2 = $dec_0 | $dec_1;
                break;

            default:
                break;
        }

        $points = \strlen(\str_replace('-', '', $dec_0)) - $decs[2];

        $dec_3 = \substr((string) $dec_2, 0, $points) . \substr((string) $dec_2, $points);

        return new Decimal($dec_3);
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function Sum(Decimal $dec)
    {
        return $this->calc($dec);
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function Multiply(Decimal $dec)
    {
        return $this->calc($dec, '*');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function Subtract(Decimal $dec)
    {
        return $this->calc($dec, '-');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function Divide(Decimal $dec)
    {
        return $this->calc($dec, '/');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function Modulus(Decimal $dec)
    {
        return $this->calc($dec, '%');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function BitwiseAnd(Decimal $dec)
    {
        return $this->calc($dec, '&');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function BitwiseOr(Decimal $dec)
    {
        return $this->calc($dec, '|');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function BitwiseXor(Decimal $dec)
    {
        return $this->calc($dec, '^');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function BitwiseNot()
    {
        $minus = $this->value[0] === '-';

        $dec = $this->Sum(new Decimal(1));

        if ($minus)
        {
            $dec = new Decimal(\str_replace('-', '', $dec->ToString()));
        }
        else
        {
            $dec = '-' . $dec;
        }

        return $dec;
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function BitwiseShiftLeft(Decimal $dec)
    {
        return $this->calc($dec, '<<');
    }

    /**
     * @param Decimal $dec
     * @return Decimal
     */
    public function BitwiseShiftRight(Decimal $dec)
    {
        return $this->calc($dec, '>>');
    }

    /**
     * @return float
     */
    public function Value()
    {
        return $this->value;
    }

}
